/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2019年1月7日
 */
package com.massyframework.beanskin.guice.beancontext;

import java.io.IOException;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import com.google.inject.Injector;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.beans.BeansException;
import com.massyframework.beanskin.assembly.handling.EnvironmentHandler;
import com.massyframework.beanskin.assembly.handling.ReadyingException;
import com.massyframework.beanskin.assembly.handling.ReadyingHandler;
import com.massyframework.beanskin.service.ServicePropertiesBuilder;
import com.massyframework.beanskin.service.ServiceRegistration;
import com.massyframework.beanskin.service.ServiceRegistry;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.web.utils.ServletRegistrationUtils;

/**
 * {@link GuiceServletHandler} 提供基于Servlet的Bean容器
 */
public final class GuiceServletHandler extends GuiceHandler
        implements EnvironmentHandler, ReadyingHandler {

    private volatile boolean runAtJ2EE = true;
    private volatile ServiceRegistration<Servlet> servletRegistration;
    private volatile ServletConfig config;
    private volatile Servlet servlet;


    /**
     * 构造方法
     */
    public GuiceServletHandler() {
    }

    /**
     * 初始化
     *
     * @throws Exception 发生非预期的例外
     */
    @Override
    protected void init() throws Exception {
        super.init();
        final Assembly assembly = this.getAssociatedAssembly();
        ServiceRepository repository = this.getServiceRepository(assembly);

        ServletContext context = repository.findService(ServletContext.class).orElse(null);
        this.runAtJ2EE = context != null;
        if (this.runAtJ2EE) {
            ServletRegistrationUtils.createServletRegistration(
                    context,
                    assembly,
                    null,
                    "true");
        }
    }

    /**
     * 析构，释放资源
     */
    @Override
    public void destroy() {
        super.destroy();
    }

    /**
     * 执行就绪
     *
     * @throws ReadyingException 就绪时发生例外
     */
    @Override
    public void readying() throws ReadyingException {
        if (!this.runAtJ2EE) {
            return;
        }

        this.exportService();
    }

    /**
     * 退出就绪
     *
     * @throws ReadyingException 退出就绪时发生的例外
     */
    @Override
    public void unreadyed() throws ReadyingException {
        this.unexportService();
    }

    /**
     * 在Web场景下注册的组件是否已准备就绪？<br>
     * Servlet的实例化要视注册时的选项，如果是首次请求时才实例化，那在发起首次请求前，本方法只能返回<code>false</code>
     *
     * @return {@link boolean},返回<code>true</code>表示已就绪，否则返回<code>false</code>
     */
    @Override
    public boolean isReady() {
        if (!this.runAtJ2EE) {
            return false;
        }

        return this.config != null;
    }

    /**
     * 创建{@link GuiceBeanContextAdapter}
     *
     * @param injector {@link Injector}
     * @return {@link GuiceBeanContextAdapter}
     */
    @Override
    protected GuiceBeanContextAdapter createBeanContext(Injector injector) throws Exception {
        GuiceBeanContextAdapter result =  super.createBeanContext(injector);

        Map<String, Servlet> servletMap = result.getBeansOfType(Servlet.class);
        if (servletMap.size() == 0){
            throw new BeansException("cannot found Servlet from guice.");
        }
        if (servletMap.size() > 1) {
            throw new BeansException("found more than one Servlet from guice.");
        }

        Servlet servlet = servletMap.values().iterator().next();
        servlet.init(this.config);
        this.servlet = servlet;
        return result;
    }

    /**
     * 取消装配件上下文注册
     */
    @Override
    protected void unregisterAssemblyContext() {
        if (this.servlet != null){
            this.servlet.destroy();
            this.servlet = null;
        }
        super.unregisterAssemblyContext();
    }

    /**
     * 处理http请求
     * @param req {@link ServletRequest}
     * @param res {@link ServletResponse}
     * @throws ServletException
     * @throws IOException
     */
    protected void doService(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        Servlet servlet = this.servlet;
        if (servlet != null) {
            servlet.service(req, res);
        }
    }

    /**
     * 输出服务
     */
    protected synchronized void exportService() {
        if (this.servletRegistration == null) {
            Assembly assembly = this.getAssociatedAssembly();
            ServiceRepository repository = this.getServiceRepository(assembly);
            ServiceRegistry registry = repository.getServiceRegistry();

            Bridge servlet = new Bridge();
            Map<String, Object> props =
                    ServicePropertiesBuilder.newInstance()
                            .name(assembly.getConfig().getParameter("servlet.name"))
                            .build();

            this.servletRegistration =
                    registry.register(Servlet.class, servlet , props);
        }
    }

    /**
     * 撤销服务输出
     */
    protected synchronized void unexportService() {
        if (this.servletRegistration != null){
            this.servletRegistration.unregister();
            this.servletRegistration = null;
        }
    }

    /**
     * 桥接器，桥接外部Servlet和内部DispatcherServletEx
     */
    private class Bridge implements Servlet {

        @Override
        public void init(ServletConfig config) throws ServletException {
            GuiceServletHandler.this.config = config;
            try {
                GuiceServletHandler.this.getLifecycleManager().start();
            } catch (Exception e) {
                logError(e.getMessage(), e);
                throw new ServletException(e.getMessage(), e);
            }
        }


        @Override
        public ServletConfig getServletConfig() {
            return config;
        }

        @Override
        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            doService(req, res);
        }

        @Override
        public String getServletInfo() {
            return null;
        }

        @Override
        public void destroy() {
            try {
                GuiceServletHandler.this.getLifecycleManager().stop();
            } catch (Exception e) {
                logError(e.getMessage(), e);
            }
        }

    }
}
